#include "bd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>

typedef struct
{
  MYSQL_BIND *resultBind;
  MYSQL_STMT *stmt;
} MYSQLResult;

BDConnection *bdConnect(BDPair *params)
{
  BDConnection *conn;
  MYSQL *mysql;
  int i;
  char *host = 0;
  char *user = 0;
  char *password = 0;
  char *dbname = 0;
  unsigned int port = 0;
  char *unix_socket = 0;
  unsigned long client_flag = 0;
  unsigned long connect_timeout = 0;

  conn = (BDConnection *)malloc(sizeof(BDConnection));
  if(!conn) goto ERR;

  mysql = mysql_init(0);
  if(!mysql) goto ERR;

  for(i=0; params[i].name; i++)
  {
    if(!strcasecmp(params[i].name,"host")) host = params[i].value;
    else if(!strcasecmp(params[i].name,"user")) user = params[i].value;
    else if(!strcasecmp(params[i].name,"password")) password = params[i].value;
    else if(!strcasecmp(params[i].name,"dbname")) dbname = params[i].value;
    else if(!strcasecmp(params[i].name,"port")) port = atoi(params[i].value);
    else if(!strcasecmp(params[i].name,"unix_socket")) unix_socket = params[i].value;
    else if(!strcasecmp(params[i].name,"client_flag")) client_flag = atol(params[i].value);
    else if(!strcasecmp(params[i].name,"connect_timeout")) connect_timeout = atol(params[i].value);
  }

  if(!mysql_real_connect(mysql, host, user, password, dbname, port, unix_socket, client_flag))
  {
    fprintf(stderr,"CONNECT error: %s\n",mysql_error(mysql));
    goto ERR;
  }

  conn->data = mysql;
  return conn;

ERR:
  if(mysql) mysql_close(mysql);
  if(conn) free(conn);

  return 0;
}

void bdDisconnect(BDConnection *connection)
{
  MYSQL *mysql = (MYSQL *)connection->data;
  if(mysql) mysql_close(mysql);
  free(connection);
}

BDConnectionStatus bdConnectionStatus(BDConnection *conn)
{
  MYSQL *mysql=(MYSQL *)conn->data;

  if(mysql_stat(mysql)) return BD_C_OK;

  return BD_C_NOT_OK;
}

BDError bdTransactionCmd(MYSQL *mysql, char *cmd)
{
  BDError result = BD_E_NONE;

  if(mysql_query(mysql, cmd))
  {
    result = BD_E_ERROR;
    fprintf(stderr,"Transaction %s error: %s\n",cmd,mysql_error(mysql));
  }

  return result;
}

BDError bdBeginTransaction(BDConnection *conn)
{
  MYSQL *mysql=(MYSQL *)conn->data;

  return bdTransactionCmd(mysql,"START TRANSACTION");
}

BDError bdCommitTransaction(BDConnection *conn)
{
  MYSQL *mysql=(MYSQL *)conn->data;

  return bdTransactionCmd(mysql,"COMMIT");
}

BDError bdRollbackTransaction(BDConnection *conn)
{
  MYSQL *mysql=(MYSQL *)conn->data;

  return bdTransactionCmd(mysql,"ROLLBACK");
}

BDTransactionStatus bdTransactionStatus(BDConnection *conn)
{
  BDTransactionStatus result = BD_T_NOT_DEFINED;

  return result;
}

//insert,delete,update
MYSQL_STMT *bdExec(BDConnection *conn, char *query, char *values[], int numParams)
{
  MYSQL *mysql = (MYSQL *)conn->data;
  int i;
  MYSQL_BIND params[1024];
  my_bool isNull[1024];

  if(numParams>1024) goto ERR;

  MYSQL_STMT *stmt = mysql_stmt_init((MYSQL *)mysql);
  if(!stmt)
  {
    fprintf(stderr,"mysql_stmt_init() error\n");
    goto ERR;
  }

  if(mysql_stmt_prepare(stmt,query,strlen(query)))
  {
    fprintf(stderr,"COMMAND error: %s\n",mysql_stmt_error(stmt));
    goto ERR;
  }

  memset(params,0,sizeof(MYSQL_BIND)*numParams);
  for(i=0; i<numParams; i++)
  {
    params[i].buffer_type=MYSQL_TYPE_STRING;
    params[i].buffer=values[i];
    params[i].buffer_length=strlen(values[i]);

    if(values[i])
    {
      isNull[i]=0;
      params[i].buffer_length=strlen(values[i]);
    }
    else
    {
      isNull[i]=1;
      params[i].buffer_length=0;
    }
    params[i].is_null=&isNull[i];

    params[i].is_unsigned=0;
    params[i].length=0;
    params[i].error=0;
  }

  if(mysql_stmt_bind_param(stmt, params))
  {
    fprintf(stderr,"COMMAND error: %s\n",mysql_stmt_error(stmt));
    goto ERR;
  }

  if(mysql_stmt_execute(stmt))
  {
    fprintf(stderr,"COMMAND error: %s\n",mysql_stmt_error(stmt));
    goto ERR;
  }

  return stmt;

ERR:
  if(stmt)
  {
    if(mysql_stmt_close(stmt))
    {
      fprintf(stderr,"Error closing statement: %s\n",mysql_stmt_error(stmt));    
    }
  }
  return 0;
}

BDError bdExecCommand(BDConnection *conn, char *query, char *values[], int numParams)
{
  MYSQL_STMT *stmt = bdExec(conn, query, values, numParams);
  if(!stmt) return BD_E_ERROR;

  if(mysql_stmt_close(stmt))
  {
    fprintf(stderr,"Error closing statement: %s\n",mysql_stmt_error(stmt));    
  }

  return BD_E_NONE;
}


MYSQL_BIND *initResultBind(long cols)
{
  int i;
  MYSQL_BIND *resultBind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND)*cols);
  if(!resultBind) goto ERR;
  memset(resultBind,0,sizeof(MYSQL_BIND)*cols);

  for(i=0; i<cols; i++)
  {
    resultBind[i].buffer_type=MYSQL_TYPE_STRING;
    resultBind[i].buffer=0;
    resultBind[i].buffer_length=0;

    resultBind[i].is_null=malloc(sizeof(my_bool));
    if(!resultBind[i].is_null) goto ERR;
    resultBind[i].length=malloc(sizeof(typeof(*resultBind[i].length)));
    if(!resultBind[i].length) goto ERR;
  }

  return resultBind;

ERR:
  if(resultBind)
  {
    for(i=0; i<cols; i++)
    {
      if(resultBind[i].is_null) free(resultBind[i].is_null);
      if(resultBind[i].length) free(resultBind[i].length);
    }
  }

  return 0;
}

//select
BDResult *bdExecQuery(BDConnection *conn, char *query, char *values[], int numParams)
{
  BDResult *result = (BDResult *)malloc(sizeof(BDResult));
  if(!result) goto ERR;

  MYSQLResult *mysqlResult = (MYSQLResult *)malloc(sizeof(MYSQLResult));
  if(!mysqlResult) goto ERR;

  MYSQL_STMT *stmt = bdExec(conn, query, values, numParams);
  if(!stmt) goto ERR;

  result->cols = mysql_stmt_field_count(stmt);

  mysqlResult->resultBind = initResultBind(result->cols);
  if(!mysqlResult->resultBind) goto ERR;
  if(mysql_stmt_bind_result(stmt, mysqlResult->resultBind)) goto ERR;

  if(mysql_stmt_store_result(stmt)) goto ERR;

  mysqlResult->stmt = stmt;
  result->rows = mysql_stmt_num_rows(stmt);
  result->data = mysqlResult;

  result->currRow = -1;

  return result;

ERR:
  if(mysqlResult) free(mysqlResult);
  if(result) free(result);
  if(stmt)
  {
    if(mysql_stmt_close(stmt))
    {
      fprintf(stderr,"Error closing statement: %s\n",mysql_stmt_error(stmt));    
    }
  }
  return 0;
}

char *bdResultColName(BDResult *result, long num)
{
  char *name = 0;
  MYSQLResult *mysqlResult = result->data;

  MYSQL_RES *res = mysql_stmt_result_metadata(mysqlResult->stmt);
  if(!res) return 0;

  MYSQL_FIELD *fields = mysql_fetch_fields(res);
  if(fields) name = fields[num].name;

  mysql_free_result(res);

  return name;
}

int bdNextRow(BDResult *result)
{
  int i;
  MYSQLResult *mysqlResult = result->data;

  result->currRow++;
  if(result->currRow>=result->rows) return 0;
/*
  for(i=0; i<result->cols; i++)
  {
    mysqlResult->resultBind[i].buffer_type=MYSQL_TYPE_STRING;
    mysqlResult->resultBind[i].buffer_length=0;
    *mysqlResult->resultBind[i].length=0;
    *mysqlResult->resultBind[i].is_null=0;
  }
*/
  int status=mysql_stmt_fetch(mysqlResult->stmt);
  if(status==1) goto ERR;

  if(status==MYSQL_DATA_TRUNCATED)
  {
    for(i=0; i<result->cols; i++)
    {
      if(*mysqlResult->resultBind[i].is_null) continue;

      long size = 1+(*mysqlResult->resultBind[i].length);
      mysqlResult->resultBind[i].buffer_type=MYSQL_TYPE_STRING;
      mysqlResult->resultBind[i].buffer=realloc(mysqlResult->resultBind[i].buffer,sizeof(char)*size);
      mysqlResult->resultBind[i].buffer_length=size;
      if(mysql_stmt_fetch_column(mysqlResult->stmt, &mysqlResult->resultBind[i], i, 0)) goto ERR;
    }
  }

  return 1;
ERR:
  return 0;
}

char *bdResultVal(BDResult *result, long col)
{
  MYSQLResult *mysqlResult = (MYSQLResult *)result->data;
  if(*mysqlResult->resultBind[col].is_null) return 0;

  return mysqlResult->resultBind[col].buffer;
}

void bdFreeResult(BDResult *result)
{
  int i;
  MYSQLResult *mysqlResult = (MYSQLResult *)result->data;

  if(mysqlResult->resultBind)
  {
    for(i=0; i<result->cols; i++)
    {
      if(mysqlResult->resultBind[i].buffer) free(mysqlResult->resultBind[i].buffer);
      if(mysqlResult->resultBind[i].is_null) free(mysqlResult->resultBind[i].is_null);
      if(mysqlResult->resultBind[i].length) free(mysqlResult->resultBind[i].length);
    }
    free(mysqlResult->resultBind);
  }

  if(mysqlResult->stmt)
  {
    if(mysql_stmt_close(mysqlResult->stmt))
    {
      fprintf(stderr,"Error closing statement: %s\n",mysql_stmt_error(mysqlResult->stmt));
    }
  }

  free(result->data);
  free(result);
}

char *bdType()
{
  static char *type = "mysql";

  return type;
}
